/* ntpclient.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <endian.h>
#include "webconfig.h"

#define VERSION_3           3
#define VERSION_4           4

#define MODE_CLIENT         3
#define MODE_SERVER         4


#define NTP_LI              0
#define NTP_VN              VERSION_3   
#define NTP_MODE            MODE_CLIENT
#define NTP_STRATUM         0
#define NTP_POLL            4
#define NTP_PRECISION       -6

#define NTP_HLEN            48

#define NTP_PORT            123
#define NTP_SERVER          "182.92.12.11"

#define TIMEOUT             10

#define BUFSIZE             1500

#define JAN_1970            0x83aa7e80

#define NTP_CONV_FRAC32(x)  (uint64_t) ((x) * ((uint64_t)1<<32))    
#define NTP_REVE_FRAC32(x)  ((double) ((double) (x) / ((uint64_t)1<<32)))   

#define NTP_CONV_FRAC16(x)  (uint32_t) ((x) * ((uint32_t)1<<16))    
#define NTP_REVE_FRAC16(x)  ((double)((double) (x) / ((uint32_t)1<<16)))    


#define USEC2FRAC(x)        ((uint32_t) NTP_CONV_FRAC32( (x) / 1000000.0 )) 
#define FRAC2USEC(x)        ((uint32_t) NTP_REVE_FRAC32( (x) * 1000000.0 )) 


#define NTP_LFIXED2DOUBLE(x)    ((double) ( ntohl(((struct l_fixedpt *) (x))->intpart) - JAN_1970 + FRAC2USEC(ntohl(((struct l_fixedpt *) (x))->fracpart)) / 1000000.0 ))   


struct s_fixedpt {
    uint16_t    intpart;
    uint16_t    fracpart;
};

struct l_fixedpt {
    uint32_t    intpart;
    uint32_t    fracpart;
};


struct ntphdr {
#if __BYTE_ORDER == __BID_ENDIAN
    unsigned int    ntp_li:2;
    unsigned int    ntp_vn:3;
    unsigned int    ntp_mode:3;
#endif
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int    ntp_mode:3;
    unsigned int    ntp_vn:3;
    unsigned int    ntp_li:2;
#endif
    uint8_t         ntp_stratum;
    uint8_t         ntp_poll;
    int8_t          ntp_precision;
    struct s_fixedpt    ntp_rtdelay;
    struct s_fixedpt    ntp_rtdispersion;
    uint32_t            ntp_refid;
    struct l_fixedpt    ntp_refts;
    struct l_fixedpt    ntp_orits;
    struct l_fixedpt    ntp_recvts;
    struct l_fixedpt    ntp_transts;
};


in_addr_t inet_host(const char *host)
{
    in_addr_t saddr;
    struct hostent *hostent;

    if ((saddr = inet_addr(host)) == INADDR_NONE) {
        if ((hostent = gethostbyname(host)) == NULL)
            return INADDR_NONE;

        memmove(&saddr, hostent->h_addr, hostent->h_length);
    }

    return saddr;
}


int get_ntp_packet(void *buf, size_t *size)
{
    struct ntphdr *ntp;
    struct timeval tv;


    if (!size || *size<NTP_HLEN)
        return -1;

    memset(buf, 0, *size);

    ntp = (struct ntphdr *) buf;
    ntp->ntp_li = NTP_LI;
    ntp->ntp_vn = NTP_VN;
    ntp->ntp_mode = NTP_MODE;
    ntp->ntp_stratum = NTP_STRATUM;
    ntp->ntp_poll = NTP_POLL;
    ntp->ntp_precision = NTP_PRECISION;

    gettimeofday(&tv, NULL);
    ntp->ntp_transts.intpart = htonl(tv.tv_sec + JAN_1970);
    ntp->ntp_transts.fracpart = htonl(USEC2FRAC(tv.tv_usec));

    *size = NTP_HLEN;

    return 0;
}


void print_ntp(struct ntphdr *ntp)
{
    time_t time;

    LOGD("LI:\t%d \n", ntp->ntp_li);
    LOGD("VN:\t%d \n", ntp->ntp_vn);
    LOGD("Mode:\t%d \n", ntp->ntp_mode);
    LOGD("Stratum:\t%d \n", ntp->ntp_stratum);
    LOGD("Poll:\t%d \n", ntp->ntp_poll);
    LOGD("precision:\t%d \n", ntp->ntp_precision);

    LOGD("Route delay:\t %lf \n",
        ntohs(ntp->ntp_rtdelay.intpart) + NTP_REVE_FRAC16(ntohs(ntp->ntp_rtdelay.fracpart)));
    LOGD("Route Dispersion:\t%lf \n",
        ntohs(ntp->ntp_rtdispersion.intpart) + NTP_REVE_FRAC16(ntohs(ntp->ntp_rtdispersion.fracpart)));
    LOGD("Referencd ID:\t %d \n", ntohl(ntp->ntp_refid));


    time = ntohl(ntp->ntp_refts.intpart) - JAN_1970;
    LOGD("Reference:\t%d %ld %s \n",
        ntohl(ntp->ntp_refts.intpart) - JAN_1970,
        FRAC2USEC(ntohl(ntp->ntp_refts.fracpart)),
        ctime(&time));

    time = ntohl(ntp->ntp_orits.intpart) - JAN_1970;
    LOGD("Originate:\t%d %d frac=%ld (%s) \n",
        ntohl(ntp->ntp_orits.intpart) - JAN_1970,
        FRAC2USEC(ntohl(ntp->ntp_orits.fracpart)),
        ntohl(ntp->ntp_orits.fracpart),
        ctime(&time) );

    time = ntohl(ntp->ntp_recvts.intpart) - JAN_1970;
    LOGD("Receive:\t%d %d (%s) \n",
        ntohl(ntp->ntp_recvts.intpart) - JAN_1970,
        FRAC2USEC(ntohl(ntp->ntp_recvts.fracpart)),
        ctime(&time) );

    time = ntohl(ntp->ntp_transts.intpart) - JAN_1970;
    LOGD("Transmit:\t%d %d (%s) \n",
        ntohl(ntp->ntp_transts.intpart) - JAN_1970,
        FRAC2USEC(ntohl(ntp->ntp_transts.fracpart)),
        ctime(&time) );
}


double get_rrt(const struct ntphdr *ntp, const struct timeval *recvtv)
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return (t4 - t1) - (t3 - t2);
}


double get_offset(const struct ntphdr *ntp, const struct timeval *recvtv)
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return ((t2 - t1) + (t3 - t4)) / 2 ;
    /*
    timezone = getTimeZone();
    timezone_ew = getTimeZoneEW();

    if(TIMEZONE_TYPE_EAST == timezone_ew)
    return ((t2 - t1) + (t3 - t4)) / 2 + (timezone*60*60);
    else
    return ((t2 - t1) + (t3 - t4)) / 2 - (timezone*60*60);
    */
}


void usage(void)
{
    fprintf(stderr,
        "Usage : ntpclient"
        " destination"
        "\n"
    );
}

int sockfd = -1;
int ntpPort = 123;
int ntp_server(char *ntpserver)
{
    int ret;
    char buf[BUFSIZE];
    char ntpaddr[256];
    size_t nbytes;
    int maxfd1;
    struct sockaddr_in servaddr;
    fd_set readfds;
    struct timeval timeout, recvtv, tv;
    double offset;

    if (NULL == ntpserver)
    {
        LOGD("ntpserver is null, use aliyun ntp\n");
        strcpy(ntpaddr, "ntp.aliyun.com");
    }
    else
    {
        #if 0
        if (NULL == strstr(ntpserver,"ntp"))
        {
            LOGE("ntpserver is not ntp, use aliyun ntp\n");
            strcpy(ntpaddr, "ntp.aliyun.com");
        }
        else
        {
            strcpy(ntpaddr, ntpserver);
        }
        #endif
        strcpy(ntpaddr, ntpserver);
    }

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(ntpPort);
    //servaddr.sin_addr.s_addr = inet_host("ntp.huashan.mlamp.cn");
    servaddr.sin_addr.s_addr = inet_host(ntpaddr);
    
    if (sockfd < 0)
    {
        if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
            LOGE("ntp_server socket error\n");
            return (-1);
        }
    }
    ret = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr));
    if (ret != 0) {
        LOGE("%s\n", strerror(errno)); 

        LOGE("ntp_server connect error ret:0x%x\n",ret);
        ntpPort++;
        if (ntpPort >= 8001)
        {
            ntpPort  = 123;
        }
        close(sockfd);
        sockfd = -1;
        return(-1);
    }

    nbytes = BUFSIZE;
    if (get_ntp_packet(buf, &nbytes) != 0) {
        LOGE("construct ntp request error \n");
        close(sockfd);
        sockfd = -1;
        return(-1);
    }
    send(sockfd, buf, nbytes, 0);


    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    maxfd1 = sockfd + 1;

    timeout.tv_sec = TIMEOUT;
    timeout.tv_usec = 0;

    if (select(maxfd1, &readfds, NULL, NULL, &timeout) > 0) {
        if (FD_ISSET(sockfd, &readfds)) {
            if ((nbytes = recv(sockfd, buf, BUFSIZE, 0)) < 0) {
                LOGE("recv error\n");
                close(sockfd);
                sockfd = -1;
                return(-1);
            }

            gettimeofday(&recvtv, NULL);
            offset = get_offset((struct ntphdr *) buf, &recvtv);

            gettimeofday(&tv, NULL);
            tv.tv_sec += (int) offset;
            //LOGD("tv.tv_sec:%d\n",tv.tv_sec);
            tv.tv_usec += offset - (int) offset;

            if (settimeofday(&tv, NULL) != 0) {
                LOGE("settimeofday error\n");
                close(sockfd);
                sockfd = -1;
                return(-1);
            }
            //LOGD("cur time:%s \n", ctime((time_t *) &tv.tv_sec));
        }
    }

    close(sockfd);

    return 0;
}
